#!/usr/local/bin/python
# Rob Dennis - Lurchington Q. Delacroix
# Keasby - TV renamer
# Project Homepage: http://code.google.com/p/keasby/

#--- Python modules
import unittest

#--- Keasby Imports
from keasby_toolbox import log

#--- Base Exception

class KeasbyError(Exception):
    """
    Base Keasby Exception
    """

#--- Base test case


class KeasbyTestCase(unittest.TestCase):
    """
    Intended to the baseclass for all test-cases. Specifies Keasby-specific
    parameters to test against. As well as specific assertions
    """

    def __init__(self, *args, **kwargs):
        """
        Initializes Keasby-specific information
        """

        #parent init
        unittest.TestCase.__init__(self, *args, **kwargs)

    def test_sanity(self):
        """
        Basic check for instantiation of the class, if self.sanity isn't
        defined at all, then it'd assumed this test wasn't expected, and
        will pass
        """

        if hasattr(self, "sanity"):
            self.assertLog(self.sanity,

                fail_message="unable to successfully initialize class instance"+
                                " in testcase %(test_case_name)s",
                fail_message_kwargs={"test_case_name":self.__class__.__name__},

                pass_message="successfully initialized %(cls_name)s instance",
                pass_message_kwargs={"cls_name":self.sanity.__class__.__name__})

        else:
            log.warning("no santity test defined for %s; passing by default",
                            self.__class__.__name__)

#---Extra Assertions

    def assertLog(self,
                    expression,
                    fail_message=None,
                    fail_message_kwargs=dict(),
                    pass_message=None,
                    pass_message_kwargs=dict(),
                    assertion=None,
                    pass_log=log.debug):
        """
        if the assertion fails for the given expression, log the
        given fail message with the given logging expression.
        Else, return True. If assert remain None, defaults
        to self.assertTrue
        """

        if not pass_message_kwargs:
            pass_message_kwargs = dict(expression=expression)

        if assertion is None:
            assertion = self.assertTrue

        if fail_message is None:
            try:
                fail_message = "Assertion: %s failed"%expression
            except TypeError:
                # cast as string and retry
                fail_message = "Assertion: %s failed"%str(expression)

        else:
            try:
                fail_message = fail_message%(fail_message_kwargs)
            except TypeError:
                # default to unfilled message
                pass


        assertion(expression, fail_message)

        if pass_message:
            pass_log(pass_message%pass_message_kwargs)

    def assertDictEquals(self, left_dict, right_dict):
        """
        Takes in two dictionaries, and compares them key by key
        """

        self.assertLog(isinstance(left_dict,dict),
                        fail_message="1st argument not a dict: %s, value=%s"%\
                                        (type(left_dict),left_dict))
        self.assertLog(isinstance(right_dict,dict),
                        fail_message="2nd argument not a dict: %s, value=%s"%\
                                        (type(right_dict),right_dict))
        try:
            self.assertEquals(len(left_dict),len(right_dict))

            logging.debug("left dictionary argument: %s",left_dict)
            logging.debug("right dictionary argument: %s",right_dict)

            for left_key,left_value in left_dict.iteritems():
                self.assertLog(left_value == right_dict.get(left_key),
                    "for key: %s, "%left_key +
                    "%s %s!=%s %s"%(left_value, type(left_value),
                                    right_dict.get(left_key),
                                    type(right_dict.get(left_key)))
                )

        except AssertionError:
            #catching errors in order to pre-pend the dictionary values

            import sys
            #hack - this index should really have been a string, but it's:
            #AssertionError(error_message) instead of error_message
            error_message = sys.exc_info()[1].message

            traceback = sys.exc_info()[2]

            _fail = "-----".join(["%s != %s"%(left_dict,right_dict),
                                    error_message])

            raise AssertionError,_fail,traceback

    def assertDictSubset(self, subdict, fulldict):
        """
        Returns True if, for all keys in subdict, the values of subdict
        and fulldict are equal
        """

        self.assertLog(isinstance(subdict,dict),
                        fail_message="1st argument not a dict: %s, value=%s"%\
                                        (type(subdict),subdict))
        self.assertLog(isinstance(fulldict,dict),
                        fail_message="2nd argument not a dict: %s, value=%s"%\
                                        (type(fulldict),fulldict))

        try:
            cut_items = [(key, fulldict[key]) for key in subdict.iterkeys()]
        except KeyError, error_message:
            raise AssertionError("key %s not found. %s != %s"%(error_message,
                                                                    subdict,
                                                                    fulldict))
        cut_fulldict = dict(cut_items)

        self.assertDictEquals(subdict,cut_fulldict)